/*--------------------------------------------- Noble Steed Engine--------------------------------------*
Authors : Daniel Randle, Alex Bourne
Date Created : Mar 14 2013

File:
	nslight.cpp

Description:
	This file contains the definition for the NSLight class and any associated functions that help
	the ol' girl out
*-----------------------------------------------------------------------------------------------------*/

#include <nslight.h>
#include <sstream>
#include <nsshader.h>
#include <nsobjectreference.h>
#include <nsmesh.h>

#ifdef DEBUG
#include <debug.h>
#endif

/*---------------------------*
|   class NSLight BEGINS     |
*---------------------------*/

NSLight::NSLight(const std::string & name, 
	const std::string & fileName, 
	const std::string & mDirectory, 
	const std::string tDirectory, 
	float diffuse, float ambient, 
	float specularPower,
	float specularIntensity,
	const NSVec3Df & _color):
	NSObject(name,fileName,mDirectory,tDirectory),
	diffuseIntensity(diffuse), 
	ambientIntensity(ambient),
	specPower(specularPower),
	specIntensity(specularIntensity),
	color(_color)
{
	LogFile("Creating base Light with name : " + objectName);
}

NSLight::~NSLight()
{
	LogFile("Deleting base Light with name : " + objectName);

	if (boundingMesh != NULL)
		delete boundingMesh;
	if (lShader != NULL)
		delete lShader;

	LogFile("Base Light Deleted");
}

void NSLight::_setUniforms()
{
	lShader->setUniform("shadowMap",NSTexture::Shadow);
	lShader->setUniform("worldPosMap",NSTexture::GWorldPos);
	lShader->setUniform("diffuseMap",NSTexture::GDiffuse);
	lShader->setUniform("normalMap",NSTexture::GNormal);

	lShader->setUniform("light.diffuseIntensity", diffuseIntensity);
	lShader->setUniform("light.ambientIntensity",ambientIntensity);
	lShader->setUniform("light.color",color);
	lShader->setUniform("light.specularIntensity",specIntensity);
	lShader->setUniform("light.specularPower",specPower);
}

void NSLight::setDiffuse(float diffuse)
{
	diffuseIntensity = diffuse;
}

void NSLight::setAmbient(float ambient)
{
	ambientIntensity = ambient;
}

void NSLight::setColor(const NSVec3Df & _color)
{
	color = _color;
}

void NSLight::setLightShader(NSShader * shader)
{
}

void NSLight::changeAmbient(float amount)
{
	ambientIntensity += amount;
}

void NSLight::changeDiffuse(float amount)
{
	diffuseIntensity += amount;
}

void NSLight::disableShader()
{
	if (lShader != NULL)
		lShader->disable();
}

void NSLight::drawLight(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos, const NSVec2Df & screenSize,NSObjectReference* ref)
{}

void NSLight::init()
{
	_loadMeshAndShader();
	lShader->enable();
	_setUniforms();
	lShader->disable();
}

void NSLight::enableShader()
{
	if (lShader != NULL)
		lShader->enable();
}

void NSLight::updateUniforms(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos,const NSVec2Df & screenSize,NSObjectReference* ref)
{
	lShader->setUniform("projCamMat", projCamMat);
	lShader->setUniform("eyeWorldPos", camPos);
	lShader->setUniform("screenSize",screenSize);
	lShader->setUniform("light.diffuseIntensity", diffuseIntensity);
	lShader->setUniform("light.ambientIntensity",ambientIntensity);
	lShader->setUniform("light.specularIntensity",specIntensity);
	lShader->setUniform("light.specularPower",specPower);
	lShader->setUniform("light.color",color);
}

float NSLight::getDiffuse() const
{
	return diffuseIntensity;
}

float NSLight::getAmbient() const
{
	return ambientIntensity;
}

NSVec3Df NSLight::getColor() const
{
	return color;
}

NSShader * NSLight::getLightShader()
{
	return lShader;
}


NSPointLight::NSPointLight(
	const std::string & name, 
	const std::string & fileName, 
	const std::string & mDirectory, 
	const std::string tDirectory, 
	float constant, 
	float linear, 
	float exp, 
	float diffuse, 
	float ambient,
	float specularPower,
	float specularIntensity,
	const NSVec3Df & _color):

	NSLight(name,fileName,mDirectory,tDirectory,diffuse,ambient,specularPower,specularIntensity,_color),
	attConstant(constant),
	attLinear(linear), 
	attExp(exp)
{
	objType = PointLight;
	LogFile("Creating PointLight with name : " + objectName);
}

NSPointLight::~NSPointLight()
{
	LogFile("Deleting PointLight with name : " + objectName);
	LogFile("PointLight Deleted");
}

void NSPointLight::_loadMeshAndShader()
{
	lShader = new NSShader("PointLightShader","pointlight.fsh","pointlight.vsh");
	boundingMesh = new NSMesh();
	boundingMesh->loadScene("Meshes/LightBounds/pointlight.dae");
}

void NSPointLight::_setUniforms()
{
	lShader->setUniform("light.position", NSVec3Df());
	lShader->setUniform("light.attConstant", attConstant);
	lShader->setUniform("light.attLinear", attLinear);
	lShader->setUniform("light.attExp", attExp);
	NSLight::_setUniforms();
}

void NSPointLight::setAttConstant(float constant)
{
	attConstant = constant;
}

void NSPointLight::setAttLinear(float linear)
{
	attLinear = linear;
}

void NSPointLight::setAttExp(float exp)
{
	attExp = exp;
}

void NSPointLight::changeAttConstant(float amount)
{
	attConstant += amount;
}

void NSPointLight::changeAttLinear(float amount)
{
	attLinear += amount;
}

void NSPointLight::changeAttExp(float amount)
{
	attExp += amount;
}

float NSPointLight::getAttConstant() const
{
	return attConstant;
}

float NSPointLight::getAttLinear() const
{
	return attLinear;
}

float NSPointLight::getAttExp() const
{
	return attExp;
}

void NSPointLight::draw()
{
	NSObject::draw();
}

void NSPointLight::drawSingle(unsigned int refID)
{
	NSObject::drawSingle(refID);
}

void NSPointLight::drawLight(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos, const NSVec2Df & screenSize, NSPointLightReference* ref)
{
	updateUniforms(projCamMat,camPos,screenSize,ref);
	if (boundingMesh != NULL)
		boundingMesh->drawSingle(ref->getLightTransform(),true);
}

void NSPointLight::updateUniforms(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos, const NSVec2Df & screenSize,NSPointLightReference* ref)
{
	lShader->setUniform("light.position", ref->getPosition());
	lShader->setUniform("light.attConstant", attConstant);
	lShader->setUniform("light.attLinear", attLinear);
	lShader->setUniform("light.attExp", attExp);
	NSLight::updateUniforms(projCamMat,camPos,screenSize,ref);
}

NSSpotLight::NSSpotLight(const std::string & name, 
	const std::string & fileName, 
	const std::string & mDirectory, 
	const std::string tDirectory, 
	float _cutoff, 
	float constant, 
	float linear, 
	float exp, 
	float diffuse, 
	float ambient,
	float specularPower,
	float specularIntensity,
	const NSVec3Df & _color,
	const NSVec3Df & _defaultDirection):
	NSPointLight(name, fileName, mDirectory, tDirectory, constant, linear, exp, diffuse, ambient, specularPower, specularIntensity, _color),
	defaultDirection(_defaultDirection)
{
	setCutoff(_cutoff);
	objType = SpotLight;
	LogFile("Creating SpotLight with name : " + objectName);
}

NSSpotLight::~NSSpotLight()
{
	LogFile("Deleting SpotLight with name : " + objectName);
	LogFile("SpotLight Deleted");
}

void NSSpotLight::_loadMeshAndShader()
{
	lShader = new NSShader("SpotLightShader","spotlight.fsh","spotlight.vsh");
	boundingMesh = new NSMesh();
	boundingMesh->loadScene("Meshes/LightBounds/spotlight.dae");
}

void NSSpotLight::_setUniforms()
{
	lShader->setUniform("light.direction", defaultDirection);
	lShader->setUniform("light.cutoff", cutoff);
	NSPointLight::_setUniforms();
}

void NSSpotLight::changeCutoff(float amount)
{
	cutoff += amount;
}

float NSSpotLight::getCutoff() const
{
	return RadiansToDegrees(acos(cutoff));
}

NSVec3Df NSSpotLight::getDefaultDirection() const
{
	return defaultDirection;
}

void NSSpotLight::setCutoff(float _cutoff)
{
	cutoff = cosf(DegreesToRadians(_cutoff));
}

void NSSpotLight::setDefaultDirection(const NSVec3Df & _defaultDirection)
{
	defaultDirection = _defaultDirection;
}

void NSSpotLight::draw()
{
	NSObject::draw();
}

void NSSpotLight::drawSingle(unsigned int refID)
{
	NSObject::drawSingle(refID);
}

void NSSpotLight::drawLight(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos, const NSVec2Df & screenSize,NSSpotLightReference* ref)
{
	updateUniforms(projCamMat,camPos,screenSize,ref);
	if (boundingMesh != NULL)
		boundingMesh->drawSingle( ref->getLightTransform() ,true);
}

void NSSpotLight::updateUniforms(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos, const NSVec2Df & screenSize,NSSpotLightReference* ref)
{
	NSVec3Df direction(defaultDirection);
	direction.rotate(ref->getOrientation());
	direction.normalize();
	lShader->setUniform("light.direction",direction);
	lShader->setUniform("light.cutoff",  cutoff);
	NSPointLight::updateUniforms(projCamMat,camPos,screenSize,ref);
}

NSDirectionLight::NSDirectionLight(
	const std::string & name,
	const std::string & fileName, 
	const std::string & mDirectory,
	const std::string & tDirectory,
	const NSVec3Df & _direction,
	float diffuse, 
	float ambient,
	float specularPower,
	float specularIntensity,
	const NSVec3Df & _color): NSLight(name,fileName,mDirectory,tDirectory,diffuse,ambient, specularPower, specularIntensity, _color),
	direction(_direction)
{
	objType = DirectionalLight;
	LogFile("Creating DirectionLight with name : " + objectName);
}

NSDirectionLight::~NSDirectionLight()
{
	LogFile("Deleting DirectionLight with name : " + objectName);
	LogFile("DirectionLight Deleted");
}

void NSDirectionLight::_setUniforms()
{
	direction.normalize();
	lShader->setUniform("direction", direction);
	NSLight::_setUniforms();
}

void NSDirectionLight::_loadMeshAndShader()
{
	lShader = new NSShader("DirectionLightShader","directionlight.fsh","directionlight.vsh","directionlight.gsh");
	boundingMesh = new NSMesh();
	boundingMesh->loadScene("Meshes/LightBounds/direction2.dae");
}

void NSDirectionLight::setDirection(const NSVec3Df & _direction)
{
	direction = _direction;
}

NSVec3Df NSDirectionLight::getDirection() const
{
	return direction;
}

void NSDirectionLight::draw()
{
	return; // do nothing
}

void NSDirectionLight::drawSingle(unsigned int refID)
{
	return; // do nothing
}

void NSDirectionLight::drawLight(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos,NSVec2Df & screenSize,NSObjectReference* ref)
{
	lShader->enable();
	updateUniforms(projCamMat, camPos, screenSize,ref);
	if (boundingMesh != NULL)
		boundingMesh->drawSingle(NSMatrix4Df(),true);
	lShader->disable();
}

void NSDirectionLight::updateUniforms(const NSMatrix4Df & projCamMat, const NSVec3Df & camPos,NSVec2Df & screenSize,NSObjectReference* ref)
{
	direction.normalize();
	lShader->setUniform("light.direction", direction);
	NSLight::updateUniforms(projCamMat,camPos,screenSize,ref);
}
